# -*- coding: utf-8 -*-
# Author: xiantingDeng
# Email: dengxianting@shxgroup.net
# Time: 2020/8/18 9:56

import time

import allure
# import pytest
from appium import webdriver
from selenium.webdriver import TouchActions
from selenium.webdriver.support.ui import WebDriverWait
# from selenium.webdriver.common.by import By
# from selenium.webdriver.support import expected_conditions as ES
from common.exception.exceptions import NotFoundElementError
from common.exception.exceptions import NotFoundTextError
from common.utils.config import appPackage
from common.utils.file_util import get_img_file_path

def singleton(class_):
    instances = {}

    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]

    return getinstance


@singleton
class Element:
    '''
    页面元素操作类
    '''

    def __init__(self, driver:webdriver.Remote, log):
        self.__log = log
        self.__driver = driver

    @staticmethod
    def sleep(num):
        return time.sleep(num)

    def reset(self, driver: webdriver.Remote):
        """因为是单例,所以当driver变动的时候,需要重置一下driver
        Args:
            driver: driver

        """
        self.driver = driver
        self.width = self.driver.get_window_size()['width']
        self.height = self.driver.get_window_size()['height']
        return self

    def _find_element(self, locator, is_need_displayed=True, ignore_error=False):
        '''
        查找单个元素，如果有多个返回第一个
        :param locator: 定位器
        :param is_need_displayed: 是否需要定位元素是否必须展示
        :param ignore_error: 找不到元素是否需要跑出异常
        :return: 元素
        '''
        value = locator["name"]
        if "timeOutInSeconds" in locator:
            wait = locator["timeOutInSeconds"]
        else:
            wait = 10
        try:
            if is_need_displayed:
                WebDriverWait(self.__driver, wait).until(
                    lambda driver: self._get_element_by_type(driver, locator).is_displayed())
            else:
                WebDriverWait(self.__driver, wait).until(
                    lambda driver: self._get_element_by_type(driver, locator) is not None)
            return self._get_element_by_type(self.__driver, locator)
        except NotFoundElementError:
            if not ignore_error:
                self.__log.warning("【element】页面中未能找到：%s" % value)
                raise
            else:
                return False

    def _find_elements(self, locator):
        '''
        查找元素（不会抛异常）
        :param locator: 定位器
        :return: 元素列表或【】
        '''
        value = locator["name"]
        if 'timeOutInSeconds' in locator:
            wait = locator['timeOutInSeconds']
        else:
            wait = 20
        try:
            WebDriverWait(self.__driver, wait).until(
                lambda driver: self._get_element_by_type(driver, locator, False).__len__() > 0)
            return self._get_element_by_type(self.__driver, locator, False)
        except NotFoundElementError:
            self.__log.warning("【element】页面中未能找到：%s" % value)
            return []

    def _get_element_by_type(self, driver, locator, element=True):
        '''
        通过locator定位元素（默认定位单个元素）
        :param driver:
        :param locator: 定位器
        :param element:
                True 查找单个元素
                False 朝招多个元素
        :return:
        '''

        value = locator['value']
        ltype = locator['type']
        if ltype == "name":
            '''
                text 正常匹配
                textContains 模糊匹配
                textStartsWith 开头匹配
            '''
            ui_value = 'new UiSelector().textContains' + '(\"' + value + '\")'
            return driver.find_element_by_android_uiautomator(
                ui_value) if element else driver.find_elements_by_android_uiautomator(ui_value)
        else:
            return driver.find_element(ltype, value) if element else driver.find_elements(ltype, value)


    # def test_SwipeGuideImages(self):
    #      for i in range(5): #滑动四次
    #         self.sleep(1)
    #         self.__driver.swipe(self.width * 9 / 10, self.height / 2, self.width / 10, self.height / 2, 1500)
    #      self.__driver.back()

    def swipe_to_top(self, direction, frequency=1):
        '''
        向什么方向滑动，以手机屏幕中心为圆点
        :param direction: 向什么方向滑动：left(左),right（右），up（上），below（下）
        :param frequency: 滑动次数
        :return:
        '''
        with allure.step("正在向{}方向滑动，滑动{}次".format(direction, frequency)):
            try:
                size = self.__driver.get_window_size()
                window_width = size.get("width")
                window_height = size.get("height")
                for i in range(int(frequency)):
                    if direction == "left":
                        self.__driver.swipe(int(window_width * 0.9), int(window_height * 0.9), int(window_width * 0.1),
                                            int(window_height * 0.9), 1000)
                    elif direction == "right":
                        self.__driver.swipe(int(window_width * 0.1), int(window_height * 0.9), int(window_width * 0.9),
                                            int(window_height * 0.9), 1000)
                    elif direction == "up":
                        self.__driver.swipe(int(window_width * 0.5), int(window_height * 0.9), int(window_width * 0.5),
                                            int(window_height * 0.1), 1000)
                    elif direction == "below":
                        self.__driver.swipe(int(window_width * 0.5), int(window_height * 0.1), int(window_width * 0.5),
                                            int(window_height * 0.9), 1000)
                    else:
                        self.__log.error("方向选择错误，")
                self.__log.info("向{}方向，滑动{}次！".format(direction, frequency))
            except Exception as e:
                self.__log.error("滑动出现异常，异常原因：{}".format(e))

    def click_element(self, locator, count=1, is_need_displayed=True, ignore_error=False):
        '''
        点击元素
        :param locator: 定位器
        :param count: 点击次数
        :param is_need_displayed: 定位元素是否必须展示
        :param ignore_error: 是否忽略异常
        :return:
        '''
        value = locator.get("name")
        with allure.step("点击-" + value):
            try:
                el = self._find_element(locator, is_need_displayed, ignore_error)
                # el = self.get_elements_wait(locator)
                if count == 1:
                    el.click()
                    self.__log.info("点击--{} 完成".format(value))
                else:
                    touch_action = TouchActions(self.__driver)
                    for i in range(count):
                        touch_action.tap(el).perform()
            except Exception as e:
                self.__log.error("点击-{} 出现异常，异常原因：{}".format(value, e))

    def smart_click_element(self, locator):
        '''
        智能点击元素
        :param locator: 定位器
        :return:
        '''
        value = locator.get("name")
        with allure.step("智能点击-" + value):
            try:
                el = self._find_element(locator, is_need_displayed=False)
                if el:
                    el.click()
                    self.__log.info("智能点击-{}完成".format(value))
            except Exception as e:
                self.__log.error("智能点击-{} 出现异常，异常原因：{}".format(value, e))

    def input_value(self, locator, values, clear_first=False, click_first=True, is_need_displayed=True,
                    ignore_error=False):
        '''
        输入明文信息
        :param locator: 定位器
        :param values: 文本内容
        :param clear_first: 是否清空内容
        :param click_first: 是否点击选中
        :param is_need_displayed: 是否需要展示控件
        :param ignore_error: 是否忽略异常
        :return:
        '''
        value = locator.get("name")
        with allure.step("{}：完成输入-{}".format(value, values)):
            try:
                el = self._find_element(locator, is_need_displayed, ignore_error)
                if clear_first:
                    el.clear()
                if click_first:
                    el.click()
                el.send_keys(values)
                self.__log.info("{}：完成输入-{}".format(value, values))
            except Exception as e:
                self.__log.error("明文输入--{}出现异常 异常原因：{}".format(value, e))

    def element_clear(self, locator, is_need_displayed=True, ignore_error=False):
        '''
        清除元素框内容
        :param locator: 定位器
        :param is_need_displayed: 是否需要展示元素
        :param ignore_error: 是否忽略异常
        :return:
        '''
        value = locator.get("name")
        with allure.step("{}:清除元素框内容".format(value)):
            try:
                el = self._find_element(locator, is_need_displayed, ignore_error)
                el.clear()
                self.__log.info("{}：清除元素框内容成功".format(value))
            except Exception as e:
                self.__log.error("{}步骤出现异常 异常原因：{}".format(value, e))

    def screenshot(self, name):
        '''
        操作步骤截图,该截图会增加到allure测试报告的log日志中
        :param name: 截图名称
        :return: 返回截图路径
        '''
        img_name = get_img_file_path(name)
        with allure.step("正在进行截图操作，{}.png".format(name)):
            self.__driver.save_screenshot(img_name)
            self.__log.info("截取屏幕成功，保存路径为：{}".format(img_name))
            with open(img_name, 'rb') as f:
                file = f.read()
                allure.attach(file, name, allure.attachment_type.PNG)
            return img_name

    def check_chckbox(self, locator, is_need_displayed=True, ignore_error=False):
        '''
        页面单选操作
        :param locator: 定位器
        :param is_need_displayed: 是否需要元素展示
        :param ignore_error: 是否忽略异常
        :return:
        '''
        value = locator.get("name")
        with allure.step("{}：完成勾选操作".format(value)):
            try:
                el = self._find_element(locator, is_need_displayed, ignore_error)
                checked = el.get_attribute("checked")
                if checked == "false":
                    el.click()
                    self.__log.info("{}：完成勾选操作".format(value))
                else:
                    self.__log.warning("{}:已处于勾选状态无需再次勾选".format(value))
            except Exception as e:
                self.__log.error("勾选操作--{}出现异常，异常原因：{}".format(value, e))

    def is_toast_show(self, message, wait=20):
        '''
        android检查是否有对应的toast显示，常用于断言
        :param message: toast信息
        :param wait: 等待时间，默认20秒
        :return: True
        '''
        locator = {'name': '[Toast] %s' % message, 'timeOutInSeconds': wait, 'type': 'xpath',
                   'value': '//*[contains(@text,\'%s\')]' % message}
        try:
            el = self._find_element(locator, is_need_displayed=False)
            return el is not None
        except NotFoundTextError:
            self.__log.error("[Toast] 页面中未能找到 %s toast" % locator)
            return False

    def switch_to_webview(self):
        '''
        切换到webview视图
        :return:
        '''
        with allure.step("切换到webview视图"):
            try:
                views = self.__driver.contexts
                target_view = None
                if len(views) > 1:
                    for view in views:
                        if appPackage in view:
                            target_view = view
                    self.__driver.switch_to.context(target_view)
                    self.__log.info("当前已切换到：{}".format(target_view))
                else:
                    self.__log.warning("当前清空不支持切换webview:{}".format(views))
            except Exception as e:
                self.__log.error("切换webview失败，原因：{}".format(e))

    def switch_to_native(self):
        '''
        恢复到默认视图（MATOVE_APP）
        :return:
        '''
        with allure.step("恢复到默认视图：MATOVE_APP"):
            self.__driver.switch_to.context("NATIVE_APP")
            self.__log.info("当前已恢复默认视图：NATIVE_APP")

    def tap_element(self, locator, msg):
        '''
        WebView页面点击元素
        :param locator: 定位器
        :param msg: 元素描述
        :return:
        '''
        with allure.step("WebView页面点击元素:{}".format(msg)):
            TouchActions(self.__driver).tap(locator).perform()
            self.__log.info("{}:点击完成".format(msg))

    def get_element_text(self, locator):
        '''
        获取元素中的text文本
        :param locator: 定位器
        :return: None或者文本内容
        '''
        value = locator.get("name")
        text = None
        with allure.step("{}:获取到的文本内容-{}".format(value, text)):
            try:
                el = self._find_elements(locator)
                if el.__len__() == 0:
                    return None
                else:
                    text = el[0].get_attribute("text")
                    return text
            except Exception as e:
                self.__log.error("获取text文本--{}：出现异常，异常原因".format(value, e))

    def input_num(self, locator, values):
        '''
        向可输入元素输入数字
        :param locator: 定位器
        :param values: 输入值
        :return:
        '''
        value = locator.get("name")
        key_map = {
            '0': 7, '1': 8, '2': 9, '3': 10, '4': 11, '5': 12, '6': 13, '7': 14, '8': 15, '9': 16
        }
        with allure.step("{}：输入数字列表{}".format(value, values)):
            try:
                el = self._find_element(locator)
                el.click()
                for v in str(values):
                    code = key_map.get(v)
                    if code:
                        self.__driver.press_keycode(code)
                self.__log.info("{}:输入{}完成".format(value, values))
            except Exception as e:
                self.__log.error("输入数字列表--{}报错，异常报错：{}".format(value, e))

    def _find_text_in_page(self, text):
        """检查页面中是否有文本关键字
        拿到页面全部source,暴力检查text是否在source中
        Args:
            text: 检查的文本

        Returns:
            True : 存在

        """
        self.__log.info("[查找] 文本--%s " % text)
        if text in self.__driver.page_source:
            self.__log.info("{}--文本已查找到".format(text))
            return True
        else:
            self.__log.warning("{}--文本未查找到".format(text))
            return False

    # todo 异常操作未完成
    def is_text_displayed(self, text, is_retry=False, retry_time=5, is_raise=False):
        '''
        检查页面中是否有文本关键字
        如果检查失败的话，不在执行case，使用is_retry = True
        :param text: 关键字（确保想要检查的关键字唯一性）
        :param is_retry:  是否重试，默认为False
        :param retry_time: 重试次数，默认为 5
        :param is_raise: 是否抛异常
        :return:
        '''
        with allure.step("检查页面是否包含--{}关键字".format(text)):
            try:
                if is_retry:
                    return WebDriverWait(self.__driver, retry_time).until(
                        lambda driver: self._find_text_in_page(text)
                    )
                else:
                    return self._find_text_in_page(text)
            except Exception as e:
                if is_raise:
                    self.__log.error("查找关键字--{}报错，报错信息：{}".format(text, e))
                    raise
                else:
                    pass

    def is_element_displayed(self, locator, is_retry=True, ):
        """检查控件是否显示
        Args:
            is_retry:是否重试检查,重试时间为'timeOutInSeconds'
            locator: 定位器
        Returns:
            true:  显示
            false: 不显示
        """
        if is_retry:
            el = self._find_element(locator, is_need_displayed=True)
            return el is not None
        else:
            el = self._get_element_by_type(self.__driver, locator)
            return el.is_displayed()

    # def return_main_menu(self):
    #     '''
    #     使用手機自帶回車鍵，返回星支付主菜單
    #     :return:
    #     '''
    #     with allure.step("回到主菜单页面"):
    #         message = {
    #             "selector": "xpath",
    #             "selector_by": "//*[@text='首页']"
    #         }
    #         while True:
    #             setting_but = self.get_elements_wait(message, )
    #             if setting_but:
    #                 self.click_element(message, msg="首页")
    #                 break
    #             self.__driver.press_keycode(4)
    #         self.__log.info("返回到星支付主菜单")
    #
    # @pytest.mark.xfail
    # @allure.severity("normal")
    # def catchEx(self):
    #     '''
    #     抛出异常，停止测试
    #     :return:
    #     '''
    #     # 存在验证码，处理验证码不执行抛出异常
    #     # 差截图
    #     self.driver_screenshot("失败截图")
    #     try:
    #         self.__log.warning_error("出现异常停止该步骤并返回首页")
    #         self.return_main_menu()
    #         assert 1 == 2
    #     except:
    #         assert 1 == 2


if __name__ == '__main__':
    capabilities = {'platformName': "Android",
                    'platformVersion': "10",
                    'deviceName': "MQS0219927003505",
                    # 'app': app,
                    'clearSystemFiles': True,
                    # 'appActivity': "com.grandale.uo.LaunchActivity",
                    # 'appPackage': "com.grandale.uo",
                    'automationName': 'UIAutomator2',
                    'noSign': True
                    }
    host = "http://localhost:4723/wd/hub"
    sdriver = webdriver.Remote(host, capabilities)
    # dict = {"name": "账号", "type": "id", "timeOutInSeconds": 1 ,"value": "com.grandale.uo:id/login_phone_edt"}
    s = {'name': '我的', 'type': 'id', 'value': 'com.grandale.uo:id/btn_my'}
    w = {'name': '始终允许', 'type': 'id', 'value': 'com.android.permissioncontroller:id/permission_allow_button'}
    from common.utils.log_util import TNLog

    el = Element(sdriver, TNLog())
    # el.click_element(w)
    print(el._find_element(s))
